package cn.jtfadmin.core.modules.quartz.service.impl;

import cn.jtfadmin.core.modules.quartz.domain.dto.JobAddPDTO;
import cn.jtfadmin.core.modules.quartz.domain.dto.JobModelRDTO;
import cn.jtfadmin.core.modules.quartz.enums.JobStatusEnum;
import cn.jtfadmin.core.modules.quartz.enums.MisfireInstructionActionEnum;
import cn.jtfadmin.core.modules.quartz.service.QuartzService;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static cn.jtfadmin.core.modules.quartz.constants.QuartzJobConstant.JOB_DATA_PARAM;

/**
 * quartz 定时任务服务实现类
 *
 * @author akfh
 * @since 1.0
 */
@Service
public class QuartzServiceImpl implements QuartzService {

    @Autowired
    private Scheduler scheduler;


    @Override
    public Scheduler getScheduler() {
        return scheduler;
    }

    @Override
    public List<JobModelRDTO> list() {
        // 定义结果集
        List<JobModelRDTO> jobModelRDTOS = new ArrayList<>();
        try {
            // 获取所有的任务时间
            Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyTriggerGroup());
            for (TriggerKey triggerKey : triggerKeys) {
                JobModelRDTO jobModelRDTO = new JobModelRDTO();
                Trigger trigger = scheduler.getTrigger(triggerKey);
                JobKey jobKey = trigger.getJobKey();

                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                Class<? extends Job> jobClass = jobDetail.getJobClass();
                String jobClassName = jobClass.getName();// 任务类
                String description = jobDetail.getDescription();// 任务描述

                jobModelRDTO.setDescription(description);
                jobModelRDTO.setJobClassName(jobClassName);

                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                if (Objects.nonNull(jobDataMap)) {
                    jobModelRDTO.setParameter(jobDataMap.getString(JOB_DATA_PARAM));
                }
                Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);

                jobModelRDTO.setStatus(JobStatusEnum.valueOf(triggerState.name()));
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    jobModelRDTO.setCronExpression(cronTrigger.getCronExpression());
                    int misfireInstruction = cronTrigger.getMisfireInstruction();
                    jobModelRDTO.setMisfireInstructionAction(MisfireInstructionActionEnum.getInstance(misfireInstruction));

                }
                Date startTime = trigger.getStartTime();
                Date endTime = trigger.getEndTime();
                Date nextFireTime = trigger.getNextFireTime();
                Date finalFireTime = trigger.getFinalFireTime();
                if (Objects.nonNull(startTime)) {
                    jobModelRDTO.setStartTime(LocalDateTime.ofInstant(startTime.toInstant(), ZoneId.systemDefault()));
                }
                if (Objects.nonNull(endTime)) {
                    jobModelRDTO.setEndTime(LocalDateTime.ofInstant(endTime.toInstant(), ZoneId.systemDefault()));
                }
                if (Objects.nonNull(nextFireTime)) {
                    jobModelRDTO.setNextFireTime(LocalDateTime.ofInstant(nextFireTime.toInstant(), ZoneId.systemDefault()));
                }
                if (Objects.nonNull(finalFireTime)) {
                    jobModelRDTO.setFinalFireTime(LocalDateTime.ofInstant(finalFireTime.toInstant(), ZoneId.systemDefault()));
                }

                jobModelRDTO.setId(triggerKey.getName());
                jobModelRDTOS.add(jobModelRDTO);
            }

            return jobModelRDTOS;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

    }

    @Override
    public String addJob(JobAddPDTO jobAddPDTO) {
        String description = jobAddPDTO.getDescription();
        if (description != null && description.length() > 200) {
            jobAddPDTO.setDescription(description.substring(0, 200));
        }
        String jobClassName = jobAddPDTO.getJobClassName();
        String id = jobAddPDTO.getId();
        if (checkExistsJob(id)) {
            return id;
        }
        try {
            Class<?> targetClass = Class.forName(jobClassName);
            if (!Job.class.isAssignableFrom(targetClass)) {
                throw new RuntimeException("任务类不正确");
            }
            if (targetClass == SpringJob.class && !SpringJob.validExecutorInfo(jobAddPDTO.getParameter())) {
                throw new RuntimeException("任务方法验证失败");
            }
            @SuppressWarnings("unchecked")
            JobBuilder jobBuilder = JobBuilder.newJob((Class<? extends Job>) targetClass);
            JobDetail jobDetail = jobBuilder.withDescription(jobAddPDTO.getDescription())
                    .requestRecovery()
                    .storeDurably()
                    .usingJobData(JOB_DATA_PARAM, jobAddPDTO.getParameter())
                    .build();
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobAddPDTO.getCronExpression());
            if (Objects.nonNull(jobAddPDTO.getMisfireInstructionAction())) {
                switch (jobAddPDTO.getMisfireInstructionAction()) {
                    case FIRE_NOW:
                        cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
                        break;
                    case DO_NOTHING:
                        cronScheduleBuilder.withMisfireHandlingInstructionDoNothing();
                        break;
                    case IGNORE_MISFIRE_POLICY:
                        cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires();
                        break;
                    default:

                }
            }
            TriggerBuilder<CronTrigger> cronTriggerTriggerBuilder = TriggerBuilder.newTrigger()
                    .forJob(jobDetail)
                    .withDescription(jobAddPDTO.getDescription())
                    .withSchedule(cronScheduleBuilder)
                    .withIdentity(id);
            if (Objects.nonNull(jobAddPDTO.getStartTime())) {
                cronTriggerTriggerBuilder.startAt(
                        Date.from(jobAddPDTO.getStartTime().atZone(ZoneId.systemDefault()).toInstant())
                );
            }
            if (Objects.nonNull(jobAddPDTO.getEndTime())) {
                cronTriggerTriggerBuilder.endAt(
                        Date.from(jobAddPDTO.getEndTime().atZone(ZoneId.systemDefault()).toInstant())
                );
            }

            CronTrigger cronTrigger = cronTriggerTriggerBuilder.build();
            scheduler.scheduleJob(jobDetail, cronTrigger);
            if (Objects.equals(jobAddPDTO.getStatus(), JobStatusEnum.PAUSED)) {
                scheduler.pauseTrigger(cronTrigger.getKey());
            }
            return id;
        } catch (ObjectAlreadyExistsException e) {
            throw new RuntimeException(e);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public void editJob(JobAddPDTO jobAddPDTO) {
        String id = jobAddPDTO.getId();
        deleteJob(id);
        addJob(jobAddPDTO);
    }

    @Override
    public String rescheduleJob(JobAddPDTO jobAddPDTO) {
        String id = jobAddPDTO.getId();
        if (!checkExistsJob(id)) {
            addJob(jobAddPDTO);
            return id;
        } else {
            editJob(jobAddPDTO);
            return jobAddPDTO.getId();
        }
    }

    @Override
    public void deleteJob(String id) {
        try {
            scheduler.unscheduleJob(TriggerKey.triggerKey(id));
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void pauseJob(String id) {
        try {
            scheduler.pauseTrigger(TriggerKey.triggerKey(id));
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void resumeJob(String id) {
        try {
            scheduler.resumeTrigger(TriggerKey.triggerKey(id));
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean checkExistsJob(String id) {
        try {
            return scheduler.checkExists(TriggerKey.triggerKey(id));
        } catch (SchedulerException e) {
            return false;
        }

    }

    @Override
    public void execute(String id) {
        try {
            Trigger trigger =
                    scheduler.getTrigger(TriggerKey.triggerKey(id));
            JobKey jobKey = trigger.getJobKey();
            Trigger newTrigger = TriggerBuilder.newTrigger().startNow().forJob(jobKey)
                    .build();
            scheduler.scheduleJob(newTrigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
